#include "spi_drv.h"

// #define DEBUG

#ifdef DEBUG
#define DEBUG_PRINTF(...)   rt_kprintf(__VA_ARGS__)
#else
#define DEBUG_PRINTF(...)
#endif

/* private rt-thread spi ops function */
static rt_err_t configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
static rt_uint32_t xfer(struct rt_spi_device *device, struct rt_spi_message *message);

static struct rt_spi_ops stm32_spi_ops =
{
    configure,
    xfer
};

rt_inline uint32_t get_spi_BaudRatePrescaler(rt_uint32_t max_hz)
{
    uint32_t SPI_BaudRatePrescaler;
    uint32_t SPI_APB_CLOCK;

    DEBUG_PRINTF("sys   freq: %d\n", HAL_RCC_GetSysClockFreq());
    DEBUG_PRINTF("pclk2 freq: %d\n", HAL_RCC_GetPCLK2Freq());

    SPI_APB_CLOCK = HAL_RCC_GetPCLK2Freq();

    if (max_hz >= SPI_APB_CLOCK / 2)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
    }
    else if (max_hz >= SPI_APB_CLOCK / 4)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
    }
    else if (max_hz >= SPI_APB_CLOCK / 8)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
    }
    else if (max_hz >= SPI_APB_CLOCK / 16)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
    }
    else if (max_hz >= SPI_APB_CLOCK / 32)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
    }
    else if (max_hz >= SPI_APB_CLOCK / 64)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
    }
    else if (max_hz >= SPI_APB_CLOCK / 128)
    {
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
    }
    else
    {
        /*  min prescaler 256 */
        SPI_BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    }

    return SPI_BaudRatePrescaler;
}

static rt_err_t configure(struct rt_spi_device *device,
                          struct rt_spi_configuration *configuration)
{
    struct rt_spi_bus *spi_bus = (struct rt_spi_bus *)device->bus;
    struct stm32_spi_bus *f4_spi_bus = (struct stm32_spi_bus *)spi_bus->parent.user_data;
    SPI_HandleTypeDef *SpiHandle = &f4_spi_bus->spi_handle;

    /* data_width */
    if (configuration->data_width <= 8)
    {
        SpiHandle->Init.DataSize = SPI_DATASIZE_8BIT;
    }
    else if (configuration->data_width <= 16)
    {
        SpiHandle->Init.DataSize = SPI_DATASIZE_16BIT;
    }
    else
    {
        return RT_EIO;
    }

    /* baudrate */
    SpiHandle->Init.BaudRatePrescaler = get_spi_BaudRatePrescaler(configuration->max_hz);

    /* CPOL */
    if (configuration->mode & RT_SPI_CPOL)
    {
        SpiHandle->Init.CLKPolarity = SPI_POLARITY_HIGH;
    }
    else
    {
        SpiHandle->Init.CLKPolarity = SPI_POLARITY_LOW;
    }
    /* CPHA */
    if (configuration->mode & RT_SPI_CPHA)
    {
        SpiHandle->Init.CLKPhase = SPI_PHASE_2EDGE;
    }
    else
    {
        SpiHandle->Init.CLKPhase = SPI_PHASE_1EDGE;
    }
    /* MSB or LSB */
    if (configuration->mode & RT_SPI_MSB)
    {
        SpiHandle->Init.FirstBit = SPI_FIRSTBIT_MSB;
    }
    else
    {
        SpiHandle->Init.FirstBit = SPI_FIRSTBIT_LSB;
    }
    SpiHandle->Init.Direction = SPI_DIRECTION_2LINES;
    SpiHandle->Init.Mode = SPI_MODE_MASTER;
    SpiHandle->Init.NSS  = SPI_NSS_SOFT;
    SpiHandle->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    SpiHandle->Init.TIMode = SPI_TIMODE_DISABLE;

    /* init SPI */
    if (HAL_SPI_Init(SpiHandle) != HAL_OK)
    {
        return RT_ERROR;
    }
    /* Enable SPI_MASTER */
    __HAL_SPI_ENABLE(SpiHandle);

    DEBUG_PRINTF("spi configuration\n");

    return RT_EOK;
}

static rt_uint32_t xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct rt_spi_bus *spi_bus = (struct rt_spi_bus *)device->bus;
    struct stm32_spi_bus *f4_spi_bus = (struct stm32_spi_bus *)spi_bus->parent.user_data;
    struct rt_spi_configuration *config = &device->config;
    SPI_TypeDef *SPI = f4_spi_bus->spi_handle.Instance;
    struct stm32_spi_cs *stm32_spi_cs = device->parent.user_data;
    rt_uint32_t size = message->length;

    /* take CS */
    if (message->cs_take)
    {
        HAL_GPIO_WritePin(stm32_spi_cs->GPIOx, stm32_spi_cs->GPIO_Pin, GPIO_PIN_RESET);
    }

    {
        if (config->data_width <= 8)
        {
            const rt_uint8_t *send_ptr = message->send_buf;
            rt_uint8_t *recv_ptr = message->recv_buf;

            while (size--)
            {
                rt_uint8_t data = 0xFF;

                if (send_ptr != RT_NULL)
                {
                    data = *send_ptr++;
                }

                // Todo: replace register read/write by stm32f4 lib
                //Wait until the transmit buffer is empty
                while ((SPI->SR & SPI_FLAG_TXE) == RESET);
                // Send the byte
                SPI->DR = data;

                //Wait until a data is received
                while ((SPI->SR & SPI_FLAG_RXNE) == RESET);
                // Get the received data
                data = SPI->DR;

                if (recv_ptr != RT_NULL)
                {
                    *recv_ptr++ = data;
                }
            }
        }
        else if (config->data_width <= 16)
        {
            const rt_uint16_t *send_ptr = message->send_buf;
            rt_uint16_t *recv_ptr = message->recv_buf;

            while (size--)
            {
                rt_uint16_t data = 0xFF;

                if (send_ptr != RT_NULL)
                {
                    data = *send_ptr++;
                }

                //Wait until the transmit buffer is empty
                while ((SPI->SR & SPI_FLAG_TXE) == RESET);
                // Send the byte
                SPI->DR = data;

                //Wait until a data is received
                while ((SPI->SR & SPI_FLAG_RXNE) == RESET);
                // Get the received data
                data = SPI->DR;

                if (recv_ptr != RT_NULL)
                {
                    *recv_ptr++ = data;
                }
            }
        }
    }

    /* release CS */
    if (message->cs_release)
    {
        HAL_GPIO_WritePin(stm32_spi_cs->GPIOx, stm32_spi_cs->GPIO_Pin, GPIO_PIN_SET);
    }

    return message->length;
}

rt_err_t stm32_spi_bus_register(SPI_TypeDef *SPI,
                                struct stm32_spi_bus *stm32_spi_bus,
                                const char *spi_bus_name)
{
    RT_ASSERT(SPI != RT_NULL);
    RT_ASSERT(spi_bus_name != RT_NULL);

    if (SPI == RT_NULL)
    {}
#ifdef RT_USING_SPI5
    else if (SPI == SPI5)
    {
        stm32_spi_bus->spi_handle.Instance = SPI5;
        stm32_spi_bus->parent.parent.user_data = (void*)stm32_spi_bus;
        __HAL_RCC_SPI5_CLK_ENABLE();
    }
#endif
    else
    {
        return RT_ENOSYS;
    }

    return rt_spi_bus_register(&stm32_spi_bus->parent, spi_bus_name, &stm32_spi_ops);
}

static int rt_hw_spi_init(void)
{
    rt_err_t result;

#ifdef RT_USING_SPI5
    /* register spi bus */
    {
        static struct stm32_spi_bus stm32_spi5_bus;
        GPIO_InitTypeDef GPIO_InitStructure;

        __HAL_RCC_GPIOF_CLK_ENABLE();

        GPIO_InitStructure.Alternate  = GPIO_AF5_SPI5;
        GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStructure.Pull  = GPIO_PULLUP;
        GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
        GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
        HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);

        result = stm32_spi_bus_register(SPI5, &stm32_spi5_bus, "spi5");
        if (result != RT_EOK)
        {
            return result;
        }
    }

    /* attach cs */
    {
        static struct rt_spi_device spi5_device0;
        static struct stm32_spi_cs  spi5_cs0;
        GPIO_InitTypeDef GPIO_InitStructure;

        GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStructure.Speed = GPIO_SPEED_FAST;

        spi5_cs0.GPIOx = GPIOF;
        spi5_cs0.GPIO_Pin = GPIO_PIN_6;

        GPIO_InitStructure.Pin = spi5_cs0.GPIO_Pin;
        HAL_GPIO_WritePin(spi5_cs0.GPIOx, spi5_cs0.GPIO_Pin, GPIO_PIN_SET);
        HAL_GPIO_Init(spi5_cs0.GPIOx, &GPIO_InitStructure);

        result = rt_spi_bus_attach_device(&spi5_device0, "spi50", "spi5", (void *)&spi5_cs0);
        if (result != RT_EOK)
        {
            return result;
        }
    }
#endif  //RT_USING_SPI5

    return RT_EOK;
}
INIT_BOARD_EXPORT(rt_hw_spi_init);

